Skip to content

test(Resizable): NOT FOR LANDING: card 81e, the grip tab rubber-bands - #10333

Closed
vjeux wants to merge 73 commits into
meta:mainfrom
vjeux:resizable/c20-81e-grip-band
Closed

vjeux wants to merge 73 commits into
meta:mainfrom
vjeux:resizable/c20-81e-grip-band

Conversation

@vjeux

@vjeux vjeux commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Stack: #9989 → #10292 → #10333

NOT FOR LANDING. Try-its for the owner's card 81e and card 81d's open choices, behind switches in Core's resize controller (createResizable.ts, resizablePane.ts) that only these stories turn on. It never merges; no record changes.

What to try

  • Card 81e, the grip tab bands: past a limit, only the small centered grip tab rubber-bands, at most about 8 px, while the full-height handle line and the panes stay put. The re-grab and the close on the lift are feat(Resizable): a pull past a limit rubber-bands and only the lift closes #10292's, unchanged.
  • 81d beside 81e on a phone: one story shows feat(Resizable): a pull past a limit rubber-bands and only the lift closes #10292's handle band above the grip tab band, each in a split sized for a phone (the panel's maximum is 55% of the viewport, up to 320 px).
  • The slide on a phone: the same split, with the band as a control (handle or grip).
  • Snap points 120, 200 and 280 px that settle on the lift, with a line at the snap the lift would pick (today's snaps jumps on every move: an API question).
  • A drag out of a collapsed panel that peeks its edge or shows a line at the size it opens to.

How it works

A page-global switch, or a story wrapper's data-try-it-band, turns these on; only these stories set them, and each clears its switch when it unmounts, so the shipping stories stay #10292's. With neither, the code paths are #10292's.

Scope

Testing

On #10292's head 20b42fa (c4a095b merged with main a19d45a), which drives the band through the handle's shared animator. This head, ab7d4e0, carries the same own diff as 28a2045 (same patch-id):

  • paneSlide in Chromium (28 passed) on this head's build; core typecheck.
  • Chromium at 28a2045, "the slide on a phone" at a 221 px maximum, 59 px past it: with band: grip, the grip tab moves 6.4 px and the handle line stays; with band: handle, the whole handle moves 6.4 px. Both return to rest after the lift.
  • On an iPhone (iOS 26.2 Simulator), the build at 6fb2455 (on 1b58c8d) of 81d beside 81e, measured frame by frame: 81e's grip tab moves 6.9 px past the maximum and 6.5 px under the minimum while the handle line stays within 0.1 px; 81d's whole handle moves 7.1 and 6.2 px.

vjeux added 30 commits November 6, 2026 02:00
Cases for a LayoutPanel that takes a useResizable region's own props: it
is measured before the collapse commits and plays from the edge facing
its ResizeHandle, on every collapse and open, a controlled isCollapsed
and a drag past the threshold, with nothing for a drag that reopens it,
the first render or a plain resize. A hand-built object, a pane at the
hook's size alone, and a LayoutPanel at the hook's size beside a handle
given the props show no slide.

Two cases for the shared edge reveal: a conceal keeps the padding and
overflow the element was laid out with and puts back the element's own
values once the motion ends, and it keeps the scroll offset, which a
reveal that reverses it scrolls back to. These cases fail until the next
commit.
…ery pointer

A LayoutPanel given a useResizable region's own props hands its element
to the region, and the region's internal factory plays `spec:AST-105`
FR9's edge reveal as it collapses or opens, under every pointer (FR5).
It measures in the commit that renders the change, before the DOM
changes, and plays once the pane renders its new size. A pointer drag
that reopens the pane follows the pointer instead, which the
ResizeHandle given the props reports to the region. The slide picks no
timing of its own: it follows the shared animated-layout module. No
prop, option, export or type changes.

The panel lends its element from a layout Effect instead of its callback
ref, both to the region and to a ResizeHandle that finds it beside it.
At width 0 the panel tells the scroll-region hook it does not scroll,
which changes that hook's ref. React then runs every merged ref again,
so a lend in the callback ref was taken back and given again
mid-collapse, which landed the slide before it played.

While the shared edge reveal holds a concealing element at the extent it
was laid out at, it now also keeps the element's padding, overflow and
scroll offset as laid out, and puts back the element's own values once
the motion ends. A LayoutPanel at width 0 drops its padding and stops
scrolling in the collapsing commit, so without that its children jumped
by the padding and a scrolled panel jumped to its top as the slide
began. A reveal that reverses a conceal scrolls back to where the
conceal kept it.
…nd open

The `resizable` description, in English, Chinese and dense form, says
that a panel given the hook's own value slides closed and open from the
edge facing its ResizeHandle, and that a hand-built object does not. A
`[feat]` changeset describes the slide.
…le in Chromium and WebKit

Real-browser proof on new stories: a start and an end panel given the
hook's value slide closed and open with the content beside them, through
a toggle, a controlled isCollapsed, a double-click, the grow arrow and
Enter. A drag reopens a pane under the pointer, and a hand-built object
collapses without a slide. Focus moves to the handle as a pane
collapses. The slide proof captures its moments by seeking the paused
motion.

Nothing inside a sliding pane moves, and a scrolled pane keeps its
scroll offset as it slides closed. The pane's shown edge stays where the
motion leaves it beside the handle: a panel without a divider overlaps
its handle by its collapse spacing only while open.
… list the slide as missing

The implementation gaps for the pane slide (Resizable FR8, LayoutPanel
FR7) close, and the current-state notes drop them. FR8 and FR7 keep their
cell widths, so no other row re-pads: each gap sentence becomes a sentence
of the same length, which says that a panel at the hook's size beside the
handle does not slide. The verification rows name the paths the browser
spec drives and the unit cases for the rest.
…takes the handle

At every captured moment of a closing and an opening slide, on both
sides, a press at the handle's middle where it shows must land on the
handle. A drag that starts there 40 ms into a closing slide, with the
motion held, must take the handle and reopen the pane. Both fail until
the next commit: the gliding content beside the handle takes the press.
A slide translates the handle and the content beside it. A translate
makes the handle a stacking context, which traps the grab zone's own
z-index inside it, and the content, later in the document and
translated too, stacked above the zone. So a finger that landed on the
handle mid-slide pressed the content, the browser took the pan, and the
drag never started. The handle now carries z-index 2 itself, as an
overlay handle already does. At rest nothing changes: 22 stories with
handles give identical screenshots and hit tests.
…nd the lift decides a close

Past the maximum, and under the minimum, the pane follows the pointer
with resistance and springs back to the limit after the lift. A drag
past the collapse threshold closes only on its lift, and a flick toward
closing closes from above it; anything else returns to the minimum. The
close plays the settle spring from the release velocity. The unit cases
and the paneSlide cases fail until the next commit: today the edge
stops at each limit, and the pane collapses mid-drag.
…nd the lift decides a close

Past the maximum, and under the minimum, the pane follows the pointer
through core's shared rubber band, over the region's basis, and the
reported size runs past the limit by what the band shows. The resize
gesture now hands the lift's velocity to the region. After the lift the
size springs back to the limit on the settle spring from that velocity,
and a new press catches it where it shows. A drag no longer collapses
the pane mid-way: the lift closes it when the pointer is past the
collapse threshold, or released fast enough to come to rest past it,
and the close slides on the settle spring from the release velocity.
The slide takes that spring through a release glide the layout
animation now accepts for one frame. Reduce Motion still lands both at
once.
…ose decided on the lift

FR8 adds the pull past either limit, through core's shared rubber band,
its spring back on springs.settle from the release velocity, and the
close a lift makes past collapsedSize, or a flick whose projected rest
is past it, on the same spring. A drag no longer collapses mid-way, and
a press on the handle mid-slide takes the handle. The collapse state
row, its verification row, the collapsedSize docs and the hook's
callback note say the lift decides. A changeset records it.
…size runs past one

While a pull rubber-bands past a limit, the region reports the size it
shows, past the bound. The separator's aria-valuenow must stay at the
bound. This case fails until the next commit.
…ize runs past one

aria-valuenow is the size clamped to the separator's bounds, so a pull
that rubber-bands past a limit announces the limit, as before. The
collapsed case already used the minimum, and keeps it.
A per-frame trace after the lift records the pane's laid-out width and
the handle's drawn middle. The pane must keep the width it was pulled
to until it takes the maximum, once, while the edge passes the places
between. The spring-back stepped from script lays out 30 widths, so
this case fails until the next commit.
The spring-back no longer steps the pane's size from script each frame.
It commits the bound once, and the pane's edge reveal plays the change
on the settle spring from the release velocity, as the close does. A
conceal holds the pane at the width it was pulled to, clips it and
rides the content beside it; a reveal below the minimum opens to it.
A finger that catches it mid-glide starts from the size it shows.
FR8 has a press on the Handle where it shows mid-slide take the Handle.
On iOS WebKit that press lands on content until animated-layout glides
resolve their hit position, so Current-state impact names the gap.
A grab on the Handle where it shows mid-slide takes it on iOS WebKit
too: the grab zone stays above the gliding content. So Current-state
impact no longer lists the gap.
…'s tempo

At tempo 1.5 a lift's close and spring-back must take 1.5 times as
long as the settle spring. This case fails until the next commit.
…s tempo

The close and the spring-back after a lift resolve `springs.settle` for
the row, so a theme's tempo times them like every other spring role.
A panel sized by the region's `size`, with no pane lent to the region,
has no rubber band: it stops at the maximum and does not jump back on
the lift. This case fails until the next commit.
The overshoot past a limit reaches the region's size only while a
LayoutPanel lends its pane, whose edge springs back on the shared glide.
A panel at the hook's size alone stops at the limit, as before, so
nothing jumps back on the lift. The pointer's raw size is now
`pointerSize`.
…zableProps

`ResizableProps._onResizeEnd` keeps main's signature. The hook keeps a
module-private map from its props to an end that takes the release
velocity, and the ResizeHandle ends a drag through `endResizeAt`, which
the package does not export. A hand-built props object ends without a
velocity.
The rubber band and its spring-back play when a LayoutPanel renders the
pane. A pane rendered at the hook's size alone stops at the limit, under
AST-094 FR5. The changeset says the same.
The `@output` headers of `resizablePane.ts`,
`animatedLayout.boundary.test.ts` and `animatedLayout.edge.test.ts`, and
`resolveMotion`'s description, ran past INV29's sentence length. Each
now says one thing per sentence.
… no reflow

Each frame of a pull past either limit and its spring-back, the pane
keeps its laid-out width, its button keeps its size, and the edge stands
at most 8 px past the limit. The content beside never overlaps the
drawn pane. These cases fail until the next commit: the pane is laid out
71 px past the maximum, and its button jumps 359 -> 288 px at the end.
…eflow

The overshoot past a limit no longer reaches the region's size. The
pane keeps its laid-out size, and its edge is drawn past the limit: the
pane stretches from its far edge on the compositor, and the handle and
the content beside it move with the edge. The rubber band's extent is
8 px, so the edge moves at most that far. After the lift the drawing
springs back on the settle spring from the release velocity, with no
layout. A finger catches it where it shows.
…t closes

FR8 records the cap and that the pane's content keeps its size past a
limit, and that only the lift closes the pane. The reported size keeps
to the limits. The changeset says the same.
`captureMotion` and `holdMotionAt` gave a change 1 s to start its
motion, then decided nothing animates. Under load a commit can land
later. They now wait, frame by frame, until a motion starts or the
handle says the collapse state changed, then two more frames for a
later commit.
This pull request carries the slide alone. The rubber band at the
limits, the close decided on the lift, their FR8 sentences and their
tests move to the pull request stacked on this one. The tree is the
slide's own commits on main 3bd058c, with the slide captures that
wait for a commit by frames.
…nd the lift decides a close

Past the maximum, and under the minimum, the pane follows the pointer
with resistance and springs back to the limit after the lift. A drag
past the collapse threshold closes only on its lift, and a flick toward
closing closes from above it; anything else returns to the minimum. The
close plays the settle spring from the release velocity. The unit cases
and the paneSlide cases fail until the next commit: today the edge
stops at each limit, and the pane collapses mid-drag.
…nd the lift decides a close

Past the maximum, and under the minimum, the pane follows the pointer
through core's shared rubber band, over the region's basis, and the
reported size runs past the limit by what the band shows. The resize
gesture now hands the lift's velocity to the region. After the lift the
size springs back to the limit on the settle spring from that velocity,
and a new press catches it where it shows. A drag no longer collapses
the pane mid-way: the lift closes it when the pointer is past the
collapse threshold, or released fast enough to come to rest past it,
and the close slides on the settle spring from the release velocity.
The slide takes that spring through a release glide the layout
animation now accepts for one frame. Reduce Motion still lands both at
once.
vjeux added 4 commits November 6, 2026 12:34
A closing lift cleared the drawn band before the slide measured the
handle, so the handle hopped about 5 px back toward open on the first
frame, then slid shut: 130.4, 135.5, then 130.6 px for a start panel,
and the same toward the end for an end panel.

The new cases pull each panel past its minimum, where the handle bands,
and under the collapse threshold, then lift with no velocity and record
every frame until the slide settles. No frame may move the handle toward
open by 0.5 px or more. Both fail until the next commit, by 5.07 and
5.05 px.
A closing lift kept the overshoot drawing only until the lift and
dropped it before the slide measured the handle, so the slide started
from the handle's laid-out place. The lift now keeps the drawing until
the pane's reveal has measured what shows, and drops it before the
layout changes; the commit that renders the change, and the next
gesture, drop it too if no measure ran. The handle's slide starts from
where it is drawn. The closing slide's start size is the size the handle
shows, so a re-grab reads the slide from there.

A close the owner keeps open, a controlled isCollapsed that stays false,
renders no slide: by the next frame the handle springs back from where
it is drawn. A unit case covers it.

Comments say the handle, not the pane's edge, moves past a limit.
The slide's changeset listed a drag past the collapse threshold among
the changes that slide the panel. A collapsible region collapses only on
the lift, so it names a lift past the collapse threshold.
FR4 and DEC-2 carry one carve-out: a gesture's release settles on
`spec:AST-094`'s spring from its release velocity; curve tokens govern
glides that no gesture releases. FR9 says only a lift closes a resizable
pane, and its conceal settles from the lift's release velocity. The
verification rows name the lift; the table keeps its column widths.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting on an engineering owner's spec decision for #10292, then one change on #10333; #9989 is approved pending CI.

#9989: approved pending CI

No blocking findings. CI has not run on this draft yet: once it is ready for review, a maintainer adds the ci:run label so CI runs.

#10292: waiting on an owner decision

Approval waits on an engineering owner approving or rejecting #10292's records as they stand now, including that they apply to mouse and trackpad input: the new component:Resizable FR8 requirements and Collapse row (a pull past either limit moves the handle about 8 px and springs back, only the lift closes, a re-grab catches the handle where it shows), and the spec:AST-105 FR4, DEC-2 and FR9 change that lets a gesture's release settle on a spring. The earlier /approve-spec on #10292 no longer counts after its latest commits; an engineering owner records the decision by commenting /approve-spec <full head SHA> on #10292, and the author may do so if they are an engineering owner.

#10333: waiting on the band choice, then one change

  • The try-it switches would ship in @astryxdesign/core, because tryIt() reads globalThis.__astryxResizeTryIt and createOvershootDrawing reads a data-try-it-band ancestor (resizablePane.ts:431, :620-632), so keep them out of package source and land only the band the owner picks.

Whole stack

The stack fails on the try-it switches in core and waits on the owner decision for #10292. A pull request lands only when it and every pull request below it are approved.

[Automated review]

FR45 and the motion-roles paragraph above it said AST-105's glide, swap
and edge reveal play the theme's curve tokens. AST-105 FR4 now carves
out a gesture's release, which settles on its spring, so both sentences
carry the same exception. The generated motion-roles page takes the
sentence from the table script and says the same.
vjeux added 5 commits November 6, 2026 18:16
The Resizable entry now carries the gesture system's velocity tracker
(1,653 B, via ResizeHandle and the resize gesture): the lift's close and
the handle's spring-back start from the release velocity. INV13 lists
velocity tracking among the gaps that components reach directly, so the
bundle test lists Resizable among the imports that use a gesture, with
that reason. It failed with the Resizable entry unlisted.
FR8's longer cells left three tables in the Resizable spec unpadded: the
public concepts, the behavioral and layout contract, and the
verification map. Prettier re-pads them; no cell text changes.
The owner's open card 81d choices, as stories only, dropped before this
lands: snap points 120, 200 and 280 px that a drag moves between freely
and the lift settles on, with a line at the snap the lift would pick and
at the closed edge; and a drag out of a collapsed panel that peeks its
edge or shows the line at the size it opens to. A page-global switch
turns them on, and only these stories set it.

For the owner's check on a phone, a story shows the slide as it ships
with its maximum at 55% of the viewport, up to 320 px, so the content
beside the panel keeps room to read. The landing stories keep 320 px.
Card 81e, for the owner to try: past a limit only the small centered
grip tab rubber-bands, at most about 8 px, while the full-height handle
line and the panes stay put. The re-grab and the close on the lift are
the same as card 81d's.

A story wrapper picks the band with `data-try-it-band`, so one phone
story shows card 81d's handle band above card 81e's grip tab band. The
phone story also takes the band as a control.
The try-it stories set the page-global switch while they rendered and
never cleared it, so a shipping story opened after one drew the target
line or settled on the lift. A try-it story now sets the switch in a
layout effect and clears it when it unmounts. The phone stories set no
switch: their band comes from the story wrapper.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested on #10333; #10292 waits on one owner answer; #9989 is approved pending CI.

#9989: approved pending CI

No blocking findings. Its last CI run stopped because CI had no room, so CI has not passed on its head yet: a maintainer re-runs all jobs.

#10292: waiting on one owner answer

Its records are owner-approved at its current head, and its two newest commits (a table re-pad and the touch-support bundle entry) change no behavior. One question stays open for an engineering owner: the rubber band past a limit and the close that waits for the lift change what a mouse and a trackpad do by default, so approval needs the owner's decision that both apply to mouse and trackpad input, or both gated to touch.

#10333: changes requested

  • The try-it switches would ship in @astryxdesign/core, because tryIt() reads globalThis.__astryxResizeTryIt and createOvershootDrawing reads a data-try-it-band ancestor (resizablePane.ts:431, :620-632), so keep them out of package source and land only the band the owner picks.

Whole stack

The stack fails on the try-it switches in core, and #10292 waits on the owner's answer. A pull request lands only when it and every pull request below it are approved.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested on #10333; #10292 waits on one owner answer; #9989 is approved.

#9989: approved

No blocking findings. Its accessibility browser-spec CI jobs failed at its current head and need a green run before it merges.

#10292: waiting on one owner answer

Its records are owner-approved at its current head, and its merge of main changes none of its own lines. One question stays open for an engineering owner: the rubber band past a limit and the close that waits for the lift change what a mouse and a trackpad do by default, so approval needs the owner's decision that both apply to mouse and trackpad input, or both gated to touch.

#10333: changes requested

  • The try-it switches would ship in @astryxdesign/core, because tryIt() reads globalThis.__astryxResizeTryIt and createOvershootDrawing reads a data-try-it-band ancestor (resizablePane.ts:431, :620-632), so keep them out of package source and land only the band the owner picks.

Whole stack

The stack fails on the try-it switches in core, and #10292 waits on the owner's answer. A pull request lands only when it and every pull request below it are approved.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested on #10333; #10292 waits on one owner answer; #9989 is approved.

#9989: approved

No blocking findings, and CI at its current head is green.

#10292: waiting on one owner answer

Its records are owner-approved at its current head, and its merge of main changes none of its own lines. One question stays open for an engineering owner: the rubber band past a limit and the close that waits for the lift change what a mouse and a trackpad do by default, so approval needs the owner's decision that both apply to mouse and trackpad input, or both gated to touch.

#10333: changes requested

  • The try-it switches would ship in @astryxdesign/core, because tryIt() reads globalThis.__astryxResizeTryIt and createOvershootDrawing reads a data-try-it-band ancestor (resizablePane.ts:431, :620-632), so keep them out of package source and land only the band the owner picks.

Whole stack

The stack fails on the try-it switches in core, and #10292 waits on the owner's answer. A pull request lands only when it and every pull request below it are approved.

[Automated review]

@vjeux

vjeux commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Closing: the maintainers chose the 81d behavior (#10292) for now. This try-it isn't for landing.

@vjeux vjeux closed this Nov 7, 2026

This branch was successfully deployed

1 active deployment
Preview — ab7d4e06 Deployed Nov 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review needs:spec-owner-review Current knowledge records await owner approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant